import httpx
import sounddevice as sd
import numpy as np
import asyncio
from io import BytesIO
import logging
from pydub import AudioSegment
from pydub.utils import make_chunks

# 日志配置（清晰显示进度）
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

# 核心配置（根据需求调整）
API_KEY = "icoolkj-3d2d71e2a20a4b1a95269db9f3f32c9c"
API_URL = "https://edgetts.api.icoolkj.cn/v1/audio/speech"
VOICE = "zh-CN-XiaoxiaoNeural"
SPEED = 1.0
PLAY_CHUNK_DURATION = 1000  # 每段播放1秒（1000毫秒，平衡实时性和完整性）

async def stream_tts(text: str):
    # 1. 第一步：先完整接收MP3流（避免分段解码截断）
    logging.info("开始请求音频流，先缓存完整数据...")
    mp3_bytes = await fetch_full_mp3(text)
    if not mp3_bytes:
        logging.error("未获取到有效音频数据，播放终止")
        return

    # 2. 第二步：解码完整MP3，按时间分段（确保帧完整，无截断）
    logging.info("音频流缓存完成，开始解码并实时播放...")
    audio_segment = AudioSegment.from_mp3(BytesIO(mp3_bytes))
    # 按1秒分割音频（MP3帧完整，不会截断）
    audio_chunks = make_chunks(audio_segment, PLAY_CHUNK_DURATION)

    # 3. 第三步：初始化播放器，逐段实时播放
    stream = None
    try:
        # 初始化音频流（用解码后的采样率和声道，确保匹配）
        stream = sd.OutputStream(
            samplerate=audio_segment.frame_rate,
            channels=audio_segment.channels
        )
        stream.start()
        logging.info(f"播放器初始化完成 | 总时长：{len(audio_segment)/1000:.1f}秒 | 采样率：{audio_segment.frame_rate}")

        # 逐段播放（无重复，无截断）
        for i, chunk in enumerate(audio_chunks, 1):
            # 转换为sounddevice支持的float32格式
            chunk_data = np.array(chunk.get_array_of_samples(), dtype=np.float32)
            chunk_data /= 32768.0  # 归一化到[-1.0, 1.0]
            # 处理声道（确保维度正确）
            if chunk.channels == 2:
                chunk_data = chunk_data.reshape(-1, 2)
            else:
                chunk_data = chunk_data.reshape(-1, 1)

            # 播放当前段
            stream.write(chunk_data)
            logging.debug(f"播放完成 | 段{i}/{len(audio_chunks)} | 时长：{len(chunk)/1000:.1f}秒")

        logging.info("✅ 全部音频播放完成，无截断！")

    except KeyboardInterrupt:
        logging.info("🔴 用户手动停止播放")
    except Exception as e:
        logging.error(f"播放错误：{str(e)[:80]}")
    finally:
        # 清理资源
        if stream:
            stream.stop()
            stream.close()
            logging.info("播放器已关闭")

async def fetch_full_mp3(text: str) -> bytes:
    """辅助函数：完整获取API返回的MP3字节流"""
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }
    params = {
        "model": "tts-1",
        "input": text.strip().replace("\n", " "),
        "voice": VOICE,
        "speed": SPEED,
        "response_format": "mp3"
    }

    try:
        async with httpx.AsyncClient() as client:
            response = await client.post(
                API_URL,
                headers=headers,
                json=params,
                timeout=60,
                follow_redirects=True  # 处理可能的重定向
            )
            if response.status_code == 200:
                # 验证是否为MP3（通过响应头或内容长度）
                if "audio/mpeg" in response.headers.get("Content-Type", "") and len(response.content) > 1024:
                    logging.info(f"成功获取MP3流 | 大小：{len(response.content)/1024:.1f}KB")
                    return response.content
                else:
                    logging.error(f"API返回非MP3数据 | 内容类型：{response.headers.get('Content-Type')}")
                    return b""
            else:
                error_detail = response.text[:200]  # 只取前200字符，避免日志过长
                logging.error(f"API请求失败 | 状态码：{response.status_code} | 详情：{error_detail}")
                return b""
    except Exception as e:
        logging.error(f"获取MP3流失败：{str(e)[:80]}")
        return b""

# 测试
if __name__ == "__main__":
    test_text = """
        欢迎大家关注旺旺小布丁的小红书！这里会分享超多实用的生活技巧、好物推荐，
        还有各种有趣的日常vlog。希望大家喜欢我的内容，记得点赞收藏加关注哦，
        你们的支持就是我更新的最大动力！后续还会有更多精彩内容，不要错过啦～
    """
    asyncio.run(stream_tts(test_text))